<?php
/*
 * This file is part of MVD_GUI.
 *
 *  MVD_GUI is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  MVD_GUI is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with MVD_GUI.  If not, see <http://www.gnu.org/licenses/>.
 */
defined( '_JEXEC' ) or die( 'Restricted access' );
/** 
 * Represent a single match
 */
class Match
{
	/* specific version of the match */
	public $version;
	/* short name of the match version */
	public $shortName;
	/* offset in the match version */
	public $offset;
	/** length of the match */
	public $length;
	/** array of strings, each one of: none,merged,deleted,added,
	found,partial,backup,parent,child; */
	public $states;
	/**
	 * Construct a match from its text description. The format is 
	 * that output by nmerge.
	 */
	function __construct( $text )
	{
		$text = trim($text,"[]\n");
		$parts = split( ":", $text );
		if ( count($parts) == 5 )
		{
			$this->shortName = $parts[0];
			$this->version = (int)substr($parts[1],8);
			$this->offset = (int)substr($parts[2],7);
			$this->length = (int)substr($parts[3],7);
			$this->states = explode(",",$parts[4]);
		}
	}
	/**
	 * Add the states of another Match with this one
	 * @param other another Match
	 */
	function mergeStates( $other )
	{
		if ( $this->states == null )
			$this->states = array();
		foreach ( $other->states as $state )
		{
			$i=0;
			for (;$i<count($this->states);$i++ )
				if ( $state == $this->states[$i] )
					break;
			if ( $i == count($this->states) )
				$this->states[] = $state;
		}
	}
	/**
	 * Convert states to string 
	 * @return a string
	 */
	function statesToString()
	{
		$string = "";
		foreach ( $this->states as $state )
		{
			if ( strlen($string) > 0 )
				$string .= ',';
			$string .= $state;
		}
		return $string;
	}
	/**
	 * Convert a match to a string in a form compatible with 
	 * the output of nmerge
	 * @return a string
	 */
	function toString()
	{
		return $this->shortName.":"."Version ".$this->version
			.":Offset ".$this->offset.":Length ".$this->length
			.":".$this->statesToString();
	}
	/**
	 * Is this match less than another i.e. is its offset 
	 * less or if equal is its length less?
	 * @return true is less
	 */
	function lessThanOrEqual( $other )
	{
		if ( $this->offset > $other->offset )
			return false;
		else if ( $this->offset < $other->offset )
			return true;
		else
		{
			if ( $this->length <= $other->length )
				return true;
			else
				return false;
		}
	}
}
/**
 * Represent a set of matches
 */
class MatchSet
{
	/** name of the mvd the matches belong to */
	public $name;
	/** set of actual Match objects */
	public $matches;
	/** pattern that resulted in these matches */
	public $pattern;
	/** current match index */
	public $match_index;
	/** specific version used for search */
	public $version;
	/**
	 * We need this proxy constructor because php doesn't support  
	 * multiple constructors out of the box.
	 */
	function __construct()
	{
		$argv = func_get_args();
		switch( func_num_args() )
		{
			default: case 1:
				self::__construct1( $argv[0] );
			break;
			case 4:
				self::__construct2( $argv[0], $argv[1], $argv[2], 
					$argv[3] );
			break;
		}
	}
	/**
	 * Construct a match set from a previously written out text 
	 * description or stored in the session object
	 * @param text a text description generated by MatchSet::toString()
	 */
	function __construct1( $text )
	{
		$text = $this->read_pattern( $text );
		$text = $this->read_name( $text );
		$text = $this->read_version( $text );
		$text = $this->read_match_index( $text );
		$this->matches = $this->read_matches( $text );
	}
	/**
	 * Construct a match set from a named set of matches in text form
	 * @param name the name of the mvd the matches came from
	 * @param text the text version of matches returned by nmerge
	 * @pattern the pattern matched to produce text
	 * @param states array of states (see above)
	 * @version the version to which offset and length refer
	 */
	function __construct2( $name, $text, $pattern, $version )
	{
		$this->name = $name;
		$this->version = ($version)?$version:0;
		$this->pattern = $pattern;
		$this->match_index = 0;
		$this->matches = $this->read_matches( $text );
	}
	/**
	 * Read the quoted pattern and pop it off the text string.
	 * This is a bit more complicated because the pattern can 
	 * contain anything.
	 * @param a string representation of a MatchSet
	 * @return the text string minus the leading pattern
	 */
	private function read_pattern( $text )
	{
		$pattern = "";
		$i = 0;
		$len = strlen( $text );
		$state = 0;
		while ( $i<=$len && $state >= 0 )
		{
			switch ( $state )
			{
				// looking for double-quote
				case 0:
					if ( $text[$i] == chr(34) )
						$state = 1;
					else
					{
						$pattern = "";
						$state = -1;
					}
					break;
				// looking for backslash or double-quote
				case 1:
					if ( $text[$i] == chr(92) )
						$state = 2;
					else if ( $text[$i] == chr(34) )
					{
						$i++;
						$text = substr($text,$i,$len-$i);
						$state = -1;
					}
					else
						$pattern .= $text[$i];
					break;
				// matched backslash
				case 2:
					if ( $text[$i] == chr(34) )
						$pattern .= '"';
					else
					{
						$pattern .= '\\';
						$pattern .= $text[$i];
					}
					$state = 1;
					break;
			}
			$i++;
		}
		$this->pattern = $pattern;
		return $text;
	}
	/**
 	 * Read the mvd name off the front of the text string
	 * @param text the text string minus the initial pattern
	 * @return the text string with the name part popped off
	 */
	private function read_name( $text )
	{
		$ind = strpos( $text, ":" );
		if ( $ind )
		{
			$this->name = substr( $text, 0, $ind );
			$text = substr( $text, $ind+1, strlen($text)-($ind+1) );
		}
		return $text;
	}
	/**
 	 * Read the version number off the front of the text string
	 * @param text the text string minus pattern and name
	 * @return the match_index + list of matches as a string
	 */
	private function read_version( $text )
	{
		$ind = strpos( $text, ":" );
		if ( $ind )
		{
			$this->version = (int)substr( $text, 0, $ind );
			$text = substr( $text, $ind+1, strlen($text)-($ind+1) );
		}
		return $text;
	}
	/**
 	 * Read the match index off the front of the text string
	 * @param text the text string minus pattern, name and version
	 * @return the states followed by list of matches as a string
	 */
	private function read_match_index( $text )
	{
		$ind = strpos( $text, "[" );
		if ( $ind )
		{
			$this->match_index = (int)substr( $text, 0, $ind );
			$text = substr( $text, $ind, strlen($text)-$ind );
		}
		return $text;
	}
	/**
	 * Turn a string of matches enclosed in square brackets into an 
	 * array of matches
	 * @param text a list of matches in the form [match-1]...[match-n]
	 * @return an array of matches
	 */
	private function read_matches( $text )
	{
		$text_matches = split( "\]\[", $text );
		$text_matches[0] = trim($text_matches[0],"[");
		$text_matches[count($text_matches)-1] = 
			trim( $text_matches[count($text_matches)-1], "]" );
		$hits = array();
		for ( $i=0;$i<count($text_matches);$i++ )
		{
			$hits[] = new Match( $text_matches[$i] );
		}
		return $hits;
	}
	/**
	 * Escape a string that may contain double-quotes
	 * so it can be safely enclosed in double-quotes
	 * @param str the string to escape
	 * @return the escaped string
	 */
	private function escape_quotes( $str )
	{
		$escaped = "";
		$len = strlen( $str );
		for ( $i=0;$i<$len;$i++ )
		{
			if ( $str[$i] == chr(34) )
				$escaped .= '\\"';
			else
				$escaped .= $str[$i];
		}
		return $escaped;
	}
	/**
	 * Increment the match index. If we reach the end of the 
	 * match set, go back to the first entry.
	 */
	private function inc_match_index()
	{
		if ( $this->match_index == count($this->matches)-1 )
			$this->match_index = 0;
		else
			$this->match_index++;
	}
	/**
	 * Convert this match set to its textual description, suitable 
	 * for storing in the session object. Prefix the text description 
	 * of the hits by the pattern in quotes followed by the name, 
	 * version and match_index, separated by ":".
	 * @return a string in the format mvd-name[match-1]..[match-n]. 
	 * Format of the matches is that of nmerge
	 */
	public function toString()
	{
		$escaped = $this->escape_quotes( $this->pattern );
		$string = '"'.$escaped.'"';
		$string .= $this->name;
		$string .= ":".$this->version;
		$string .= ":".$this->match_index;
		foreach ( $this->matches as $match )
			$string .= "[".$match->toString()."]";
		return $string;
	}
	/**
	 * Get the next match and increment the match index
	 * @return a single Match
	 */
	public function getNextMatch()
	{
		$match = $this->matches[$this->match_index];
		$this->inc_match_index();
		return $match;
	}
}

